/******************************************************************************
* echo_server.c                                                               *
*                                                                             *
* Description: This file contains the C source code for an echo server.  The  *
*              server runs on a hard-coded port and simply write back anything*
*              sent to it by connected clients.  It does not support          *
*              concurrent clients.                                            *
*                                                                             *
* Authors: Athula Balachandran <abalacha@cs.cmu.edu>,                         *
*          Wolf Richter <wolf@cs.cmu.edu>                                     *
*                                                                             *
*******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#include "parse.h"

#define ECHO_PORT 9999
#define BUF_SIZE 4096
#define MAX_HEADER_BYTE 8192

int sock = -1, client_sock = -1;
char buf[BUF_SIZE];

int exception=200;
const int MAX_RESPINSEBODY_BYTE=8192;

int close_socket(int sock) {
    if (close(sock)) {
        fprintf(stderr, "Failed closing socket.\n");
        return 1;
    }
    return 0;
}
void handle_signal(const int sig) {
    if (sock != -1) {
        fprintf(stderr, "\nReceived signal %d. Closing socket.\n", sig);
        close_socket(sock);
    }
    exit(0);
}
void handle_sigpipe(const int sig) 
{
    if (sock != -1) {
        return;
    }
    exit(0);
}
int main(int argc, char *argv[]) {
    //TODO 自己的这部分代码放进来
    /* register signal handler */
    /* process termination signals */
    signal(SIGTERM, handle_signal);
    signal(SIGINT, handle_signal);
    signal(SIGSEGV, handle_signal);
    signal(SIGABRT, handle_signal);
    signal(SIGQUIT, handle_signal);
    signal(SIGTSTP, handle_signal);
    signal(SIGFPE, handle_signal);
    signal(SIGHUP, handle_signal);
    /* normal I/O event */
    signal(SIGPIPE, handle_sigpipe);
    //ssize_t readret;
    socklen_t cli_size;
    struct sockaddr_in addr, cli_addr;

    fprintf(stdout, "----- Echo Server -----\n");
    
    /* all networked programs must create a socket */
    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        fprintf(stderr, "Failed creating socket.\n");
        return EXIT_FAILURE;
    }
    /* set socket SO_REUSEADDR | SO_REUSEPORT */
    int opt = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
    {
        fprintf(stderr, "Failed setting socket options.\n");
        return EXIT_FAILURE;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(ECHO_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    /* servers bind sockets to ports---notify the OS they accept connections */
    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)))
    {
        close_socket(sock);
        fprintf(stderr, "Failed binding socket.\n");
        return EXIT_FAILURE;
    }


    if (listen(sock, 5))
    {
        close_socket(sock);
        fprintf(stderr, "Error listening on socket.\n");
        return EXIT_FAILURE;
    }

    /* finally, loop waiting for input and then write it back */
    while (1)
    {
        exception=200;
        cli_size = sizeof(cli_addr);
        fprintf(stdout,"Waiting for connection...\n");
        client_sock = accept(sock, (struct sockaddr *) &cli_addr,&cli_size);
        if (client_sock== -1)
        {
            close(sock);
            fprintf(stderr, "Error accepting connection.\n");
            return EXIT_FAILURE;
        }
        fprintf(stdout,"New connection from %s:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));

        while(1)
        {
            memset(buf, 0, BUF_SIZE);
            int readret = recv(client_sock, buf, BUF_SIZE, 0);
            if (readret <0)break;
            fprintf(stdout,"Received (total %d bytes):%s \n",readret,buf); 
            Request* request=parse(buf,readret,sock);

            if(request==NULL){
                exception=400;
                break;
            }

            if(strcmp(request->http_version,"HTTP/1.1")){
                exception=505;
                break;
            }

            if(sizeof(request->headers)>MAX_HEADER_BYTE){
                exception=400;
                break;
            }

            int isImplement=1;
            if(!strcmp(request->http_method,"GET")){
                memset(buf, 0, BUF_SIZE);
                
                char msg[MAX_RESPINSEBODY_BYTE];
                char url[100]="./static_site/";
                if(!strcmp(request->http_uri,"/"))
                    strcat(url,"index.html");
                else
                    strcat(url,request->http_uri);

                struct stat stat_buf;
                if(stat(url,&stat_buf)==-1){
                    switch (errno)
                    {
                    case ENOENT:
                        fprintf(stdout,"No file %s exists.\n",url);break;
                    case ENOTDIR:
                        fprintf(stdout,"The dictory in the url isn't real.\n");break;
                    case ELOOP:
                        fprintf(stdout,"There are too many symbol problems in wanted open file.\n");break;
                    case EFAULT:
                        fprintf(stdout,"%s us invaild pointer.\n");break;
                    default:
                        fprintf(stdout,"No matched errno.\n");
                    }
                    exception=404;
                    memset(buf, 0, BUF_SIZE);
                    break;
                }
                
                const int fd_in = open(url, O_RDONLY);
                if(fd_in < 0) {
	            	fprintf(stdout,"Failed to open the file\n");
		            exception=404;
                    memset(buf, 0, BUF_SIZE);
                    break;
	            }

                const int readRet = (int) read(fd_in, msg, BUFSIZ);
                
                
                strcat(buf,"HTTP/1.1 200 OK");
                strcat(buf,"\r\nServer: liso/1.1");

                strcat(buf,"\r\nDate: ");
                time_t now_date;
                struct tm *now_info;
                char date_buf[35];
                time(&now_date);        
                now_info = localtime(&now_date);     
                strftime(date_buf, 80, "%a, %d %b %Y %H:%M:%S GMT", now_info);
                strcat(buf, date_buf);

                strcat(buf,"\r\nContent-Length: ");
                char len_buf[10];
                sprintf(len_buf, "%ld", stat_buf.st_size);
                strcat(buf,len_buf);

                strcat(buf,"\r\nContent-type: ");
                char ext[10];
                int n=sizeof(url)/sizeof(url[0]);
                int i;
                for(i=n-1;i>=0;i--)
                {
                   if(url[i]=='.')
                        break;
                }
                strcpy(ext,url+i+1);
                char file_type[20];
                if(!strcmp(ext,"html")||!strcmp(ext,"css"))
                    strcat(file_type,"text/");
                else if(!strcmp(ext,"png")||!strcmp(ext,"jpeg")||!strcmp(ext,"gif"))
                    strcat(file_type,"image/");
                strcat(file_type,ext);
                strcat(buf,file_type);
                
                strcat(buf,"\r\nLast-modified: ");
                time_t modi_date=stat_buf.st_mtime;
                struct tm *modi_info;
                char modi_buf[35];
                time(&modi_date);        
                modi_info = gmtime(&modi_date);     
                strftime(modi_buf, 80, "%a, %d %b %Y %H:%M:%S GMT", modi_info);
                strcat(buf, modi_buf);
                
                strcat(buf,"\r\nConnection: keep-alive\r\n\r\n");
                strcat(buf,msg);
            }

            else if(!strcmp(request->http_method,"HEAD")){
                memset(buf, 0, BUF_SIZE);
                strcat(buf,"HTTP/1.1 200 OK");
                strcat(buf,"\r\nServer: liso/1.1");

                strcat(buf,"\r\nDate: ");
                time_t now_date;
                struct tm *now_info;
                char date_buf[35];
                time(&now_date);        
                now_info = localtime(&now_date);     
                strftime(date_buf, 80, "%a, %d %b %Y %H:%M:%S GMT", now_info);
                strcat(buf, date_buf);
                
                struct stat stat_buf;
                strcat(buf,"\r\nContent-Length: ");
                char len_buf[10];
                sprintf(len_buf, "%d", stat_buf.st_size);
                strcat(buf,len_buf);

                strcat(buf,"\r\nContent-type: ");

                strcat(buf,"\r\nLast-modified: ");
                time_t modi_date=stat_buf.st_mtime;
                struct tm *modi_info;
                char modi_buf[35];
                time(&modi_date);        
                modi_info = gmtime(&modi_date);     
                strftime(modi_buf, 80, "%a, %d %b %Y %H:%M:%S GMT", modi_info);
                strcat(buf, modi_buf);
                
                strcat(buf,"\r\nConnection: keep-alive\r\n");
            }
            else if(!strcmp(request->http_method,"POST")){
                
            }
            else
                isImplement=0;;

            if(!isImplement){
                exception=501;
                break;
            }

            if(send(client_sock, buf, strlen(buf), 0) < 0)break;
            fprintf(stdout,"Send back\n");
        } 

        memset(buf, 0, BUF_SIZE);
        switch (exception)
        {
            case 400:
                strcat(buf, "HTTP/1.1 400 Bad request\r\n\r\n"); break;
            case 404:
                strcat(buf,"HTTP/1.1 404 Not Found\r\n\r\n"); break;
            case 501:
                strcat(buf, "HTTP/1.1 501 Not Implemented\r\n\r\n"); break;
            case 505:
                strcat(buf,"HTTP/1.1 505 HTTP Version not supported\r\n\r\n"); break;
        }
        if (send(client_sock, buf, strlen(buf), 0) < 0){
                fprintf(stdout,"Send back failed");
                break;
        }

        if (close_socket(client_sock))
        {
            close_socket(sock);
            fprintf(stderr, "Error closing client socket.\n");
            return EXIT_FAILURE;
        }
        fprintf(stdout,"Closed connection from %s:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
    }

    close_socket(sock);

    return EXIT_SUCCESS;
}